package com.github.cyf.util;

import ch.qos.logback.core.util.FileUtil;
import com.github.cyf.config.minio.MinioProperties;
import com.github.cyf.domain.entity.UploadResult;
import com.github.cyf.exception.OssException;
import io.minio.*;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * @author chenyifan
 * @create 2024-03-16 18:39
 */
@Component
@RequiredArgsConstructor
public class MinioUtil {

    private final MinioClient minioClient;
    private final MinioProperties properties;

    public UploadResult upload(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        try {
            return upload(file.getBytes(), FilenameUtils.getExtension(originalFilename), file.getContentType());
        } catch (IOException e) {
            throw new OssException(e.getMessage());
        }
    }

    public UploadResult uploadBySuffix(byte[] data, String extension, String contentType) {
        return upload(data, getUniquePath(properties.getPrefix(), extension), contentType);
    }


    public UploadResult upload(byte[] data, String path, String contentType) {
        return upload(new ByteArrayInputStream(data), path, contentType);
    }

    public UploadResult upload(InputStream inputStream, String path, String contentType) {
        if (!(inputStream instanceof ByteArrayInputStream)) {
            try {
                inputStream = new ByteArrayInputStream(StreamUtils.copyToByteArray(inputStream));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            PutObjectArgs args = PutObjectArgs.builder()
                    .contentType(contentType) // 设置上传对象的内容类型
                    .bucket(properties.getBucketName()) // 上传到的目标存储桶名称
                    .object(path) // 文件在存储桶中的路径
                    // 参数二：文件总字节数，在进行多部分（或分块）上传时，服务器需要知道最终文件的总大小来正确地组合所有部分
                    // 参数三：将分块大小设为 -1，SDK内部会根据输入流的实际情况动态调整分块大小以优化上传性能。
                    .stream(inputStream, inputStream.available(), -1)
                    .build();
            minioClient.putObject(args);
        } catch (Exception e) {
            throw new OssException("上传文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
        return buildResultEntity(path);
    }

    /**
     * 下载文件
     * @param object 对象名称，相对于 bucket 的路径
     * @param res
     */
    public void download(String object, HttpServletResponse res) {
        GetObjectArgs args = GetObjectArgs.builder().bucket(properties.getBucketName()).object(object).build();
        try (GetObjectResponse response = minioClient.getObject(args)){
            byte[] data = StreamUtils.copyToByteArray(response);
            res.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode(FilenameUtils.getName(object), StandardCharsets.UTF_8.name());
            res.addHeader("Content-Disposition", "attachment;fileName=" +  fileName);
            res.getOutputStream().write(data);
        } catch (Exception e) {
            throw new OssException("下载失败：" + e.getMessage());
        }
    }

    /**
     * 删除文件对象
     * @param object 对象名称，相对于 bucket 的路径
     */
    public void removeObject(String object) {
        RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(properties.getBucketName()).object(object).build();
        try {
            minioClient.removeObject(args);
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    /**
     * 预览图片，获取预览地址
     * @param object 对象名称，相对于 bucket 的路径
     * @return       url地址
     */
    public String preview(String object){
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(properties.getBucketName())
                .object(object)
                .method(Method.GET).build();
        try {
            return minioClient.getPresignedObjectUrl(args);
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    /**
     * 判断 bucket 是否存在
     * @param bucketName
     * @return
     */
    public boolean existBucket(String bucketName) {
        try {
            BucketExistsArgs args = BucketExistsArgs.builder().bucket(bucketName).build();
            return minioClient.bucketExists(args);
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    /**
     * 创建 bucket
     * @param bucketName
     * @return
     */
    public void createBucket(String bucketName) {
        try {
            MakeBucketArgs args = MakeBucketArgs.builder().bucket(bucketName).build();
            minioClient.makeBucket(args);
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    /**
     * 删除存储 bucket
     * @param bucketName
     */
    public void removeBucket(String bucketName) {
        try {
            RemoveBucketArgs args = RemoveBucketArgs.builder().bucket(bucketName).build();
            minioClient.removeBucket(args);
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    /**
     * 根据保存路径构建返回结果
     * @param path
     * @return
     */
    private UploadResult buildResultEntity(String path) {
        UploadResult result = new UploadResult();
        result.setUrl(properties.getEndpoint() + "/" + properties.getBucketName() + "/" + path);
        result.setFilename(path); // 这里是相对 bukect 的路径
        return result;
    }

    /**
     * 获取唯一路径
     * prefix:"p2", fileSuffix:".txt"  —> /p2/yyyy/MM/dd/uuid.txt
     * prefix:null, fileSuffix:".txt"  —> /yyyy/MM/dd/uuid.txt
     * @param prefix 路径前缀，可为空
     * @param extension 文件后缀
     * @return
     */
    private String getUniquePath(String prefix, String extension) {
        String fileName = UUID.randomUUID() + "." + extension;
        String path = "/";
        if (StringUtils.hasText(prefix)) {
            if (prefix.endsWith("/")) {
                prefix = prefix.substring(0, prefix.length() - 1);
            }
            path = path + prefix;
        }
        LocalDate now = LocalDate.now();
        return path + "/" + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/" + fileName;
    }
}